// This is the first part of the middlewares that try to work out the contract
// the reason is, it will get re-use by subsequences middlewares
// so we might as well do this here
// Also we could hijack it off and serve the html files up for documentation purpose

const fsx = require('fs-extra')
const { join } = require('path')
const { trim } = require('lodash')
const { CONTRACT_NAME, SHOW_CONTRACT_DESC_PARAM } = require('jsonql-constants')
const { JsonqlContractAuthError } = require('jsonql-errors')
const { isObjectHasKey } = require('jsonql-utils')
const {
  getDebug,
  getContract,
  handleOutput,
  packResult,
  isContractJson,
  ctxErrorHandler
} = require('./lib')

const debug = getDebug('contract-middleware')

/**
 * remove the description field
 * @param {boolean} showDesc true to keep
 * @param {object} contract json
 * @return {object} clean contract
 */
const removeDesc = (showDesc, contract) => {
  debug('showDesc', showDesc)
  if (showDesc) {
    return contract;
  }
  let c = contract;
  for (let type in c) {
    for (let fn in c[type]) {
      if (isObjectHasKey(c[type][fn], 'description')) {
        delete c[type][fn].description;
        if (c[type][fn].returns && isObjectHasKey(c[type][fn].returns, 'description')) {
          delete c[type][fn].returns.description;
        }
      }
    }
  }
  return c;
}

/**
 * get the contract data @TODO might require some sort of security here
 * @param {object} opts options
 * @param {object} ctx koa
 * @return {undefined}
 */
const handleContract = (opts, ctx, contract) => {
  // @1.3.2 add a filter here to exclude the description field
  // just to reduce the size, but we serve it up if this is request with
  // desc=1 param - useful for the jsonql-web-console
  // debug('handleContract', ctx.query.desc)
  const key = Object.keys(SHOW_CONTRACT_DESC_PARAM)[0]
  let desc = !!(ctx.query[key] && ctx.query[key] === SHOW_CONTRACT_DESC_PARAM[key])
  handleOutput(opts)(ctx, packResult(
    removeDesc(desc, contract)
  ))
}

/**
 * Search for the value from the CONTRACT_KEY_NAME
 * @param {object} ctx koa context
 * @param {string} contractKeyName the key to search
 * @return {string} the value from header
 */
const searchContractAuth = function(ctx, contractKeyName) {
  // debug(`Try to get ${contractKeyName} from header`, ctx.request.header);
  return ctx.request.get(contractKeyName)
}

/**
 * Handle contract authorisation using a key
 * @param {object} ctx koa
 * @param {object} opts options
 * @return {boolean} true ok
 */
const contractAuth = function(ctx, opts) {
  if (opts.contractKey !== false && opts.contractKeyName !== false) {
    const { contractKey, contractKeyName } = opts;
    // debug('Received this for auth', contractKeyName, contractKey);
    // @2019-05-08 we change from url query to header
    // const keyValueFromClient = trim(ctx.query[contractKeyName]);
    // debug('the query value', ctx.query);
    const keyValueFromClient = searchContractAuth(ctx, contractKeyName)

    switch (true) {
      case typeof contractKey === 'string':
          // debug('compare this two', keyValueFromClient, contractKey);
          return keyValueFromClient === contractKey;
        break;
      default:
        // @TODO what if we want to read the header?
        debug('Unsupported contract auth type method', typeof contractKey)
        return false;
    }
  }
  return true;
}

/**
 * @TODO is there a bug in here somewhere that I am not aware of
 * it seems to me that everytime the middleware get call, it keep trying to
 * generate contract, or reading from the contract, which is not ideal
 * it should able to cache it some how?
 */
module.exports = function(opts) {
  // export
  return async function(ctx, next) {
    // this will only handle certain methods
    const { isReq, resolverType } = ctx.state.jsonql;
    // @2019-05-24 We need to make sure the call is actually a jsonql call
    // because when http access happen it could make multiple call to the
    // server and contract generator just run multiple times on a very short time
    // and cause the file read failure
    if (isReq) {
      // now is this request asking for the public contract
      if (resolverType === CONTRACT_NAME) {
        if (contractAuth(ctx, opts)) {
          let publicContractJson = ctx.state.jsonql.publicContract;
          if (!publicContractJson) {
            debug(`call the get public contract here`, opts.name)
            // This should be a one off event
            publicContractJson = await getContract(opts, true)
            ctx.state.jsonql.publicContract = publicContractJson;
          }
          // debug('call handle public contract method here');
          // this is a public contract
          return handleContract(opts, ctx, publicContractJson)
        } else {
          return ctxErrorHandler(ctx, 'JsonqlContractAuthError')
        }
      }
    }
    await next()
  }
}
